/*
 * MIT License
 *
 * Copyright (c) 2020 wen.gu <454727014@qq.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

 /***************************************************************************
 * Name: ngl_message.c
 *
 * Purpose: ngl message operation API implementation
 *
 * Developer:
 *   wen.gu , 2023-04-09
 *
 * TODO:
 *
 ***************************************************************************/
#include <stdlib.h>

#include "ngl/ngl_message.h"

/** to process  start code and end code */
#define NGL_MSG_PREFIX_SIZE 1
#define NGL_MSG_SUFFIX_SIZE 1

#define NGL_MSG_EXTENSION_DATA_SIZE (NGL_MSG_PREFIX_SIZE + NGL_MSG_SUFFIX_SIZE)


struct _ngl_message_s {
    ngl_base_header_t base_header;
    uint8_t* extension_header;
    uint16_t ext_hdr_len;
    uint8_t* payload; /**< payload buffer, message::buffer + sizeof(base header) + sizeof(extension header) */
    uint16_t payload_len;
    uint8_t* buffer;
    uint16_t length; /**< sizeof(base header) + sizeof(extension header) + sizeof (payload) */
} ;

/***************************************************************************
 * inner function  implementation
 ***************************************************************************/
static uint8_t* ngl_ext_hdr_fill_data(uint8_t* ext_hdr_buf, const uint8_t* data, uint8_t size) {
    *ext_hdr_buf++ = size;
    memcpy(ext_hdr_buf, data, size);
    ext_hdr_buf += size;
    return ext_hdr_buf;    
}

static uint8_t* ngl_ext_hdr_fill_data_without_len_field(uint8_t* ext_hdr_buf, const uint8_t* data, uint8_t size) {
    memcpy(ext_hdr_buf, data, size);
    ext_hdr_buf += size;
    return ext_hdr_buf;    
}

static const uint8_t* ngl_ext_hdr_read_data(const uint8_t* ext_hdr_buf, uint8_t* out_buf, uint8_t* field_size) {
    *field_size = *ext_hdr_buf++;
    memcpy(out_buf, ext_hdr_buf, *field_size);
    ext_hdr_buf += *field_size;
    return ext_hdr_buf;
}

static const uint8_t* ngl_ext_hdr_read_data_without_len_field(const uint8_t* ext_hdr_buf, uint8_t* out_buf, uint8_t field_size) {
    memcpy(out_buf, ext_hdr_buf, field_size);
    ext_hdr_buf += field_size;
    return ext_hdr_buf;
}

static void ngl_msg_initialize_with_buffer(ngl_message_t* msg, uint8_t* buf, uint16_t size) {
    msg->buffer = buf;
    msg->length = size;
    msg->extension_header = buf + NGL_MSG_PREFIX_SIZE + NGL_BASE_HDR_LEN;
    msg->ext_hdr_len = msg->base_header.elen;
    msg->payload = msg->extension_header + msg->ext_hdr_len;
    msg->payload_len = (size - NGL_MSG_EXTENSION_DATA_SIZE) - NGL_BASE_HDR_LEN - msg->ext_hdr_len;
}

/***************************************************************************
 * API function  implementation
 ***************************************************************************/
ngl_message_t* ngl_message_create(ngl_base_header_t* base_hdr) {
    if (!base_hdr) {
        return NULL;
    }

    ngl_message_t* msg = (ngl_message_t*)malloc(sizeof(ngl_message_t));

    if (!msg) {
        return NULL;
    }

    uint32_t buf_len = base_hdr->mlen + NGL_MSG_EXTENSION_DATA_SIZE;
    uint8_t* msg_buf = (uint8_t*)malloc(buf_len);

    if (!msg_buf) {
        free(msg);
        return NULL;
    }

    msg_buf[0] = NGL_MSG_SYNC_CODE;
    msg_buf[buf_len - NGL_MSG_SUFFIX_SIZE] = NGL_MSG_END_CODE;

    
    memcpy(&msg->base_header, base_hdr, NGL_BASE_HDR_LEN);


    ngl_msg_initialize_with_buffer(msg, msg_buf, buf_len);

    return msg;
}

ngl_error_t ngl_message_destroy(ngl_message_t* msg) {
    if (!msg) {
        return NGL_ErrInvalidArgument;
    }

    if (msg->buffer) {
        free(msg->buffer);
    }

    free(msg);
    return NGL_ErrOK;
}

ngl_base_header_t* ngl_message_base_header(ngl_message_t* msg) {
    if (!msg) {
        return NULL;
    }

    return &msg->base_header;
}


ngl_error_t ngl_message_set_extension_header(ngl_message_t* msg, const ngl_extension_header_t* ext_hdr) {
    if (!msg || !ext_hdr) {
        return NGL_ErrInvalidArgument;
    }

    uint16_t htyp = ext_hdr->htyp;

    if (!NGL_HTYP_HAS_EXT_HDR(htyp)) {
        return NGL_ErrInvalidArgument;
    }

    uint16_t ext_hdr_len = ngl_extension_header_get_length(ext_hdr);
    if (msg->ext_hdr_len != ext_hdr_len) {
        return NGL_ErrOutOfRange;
    }

    uint8_t* ext_hdr_buf = msg->extension_header;

    if (NGL_IS_HTYP_WHID(htyp)){
        ext_hdr_buf = ngl_ext_hdr_fill_data(ext_hdr_buf, ext_hdr->host_name, ext_hdr->host_name_len);
    }

    if (NGL_IS_HTYP_WACID(htyp)){
        ext_hdr_buf = ngl_ext_hdr_fill_data(ext_hdr_buf, ext_hdr->application_id, ext_hdr->appid_len);
        ext_hdr_buf = ngl_ext_hdr_fill_data(ext_hdr_buf, ext_hdr->context_id, ext_hdr->ctxid_len);
    }

    if (NGL_IS_HTYP_WMID(htyp)) {
        ext_hdr_buf = ngl_ext_hdr_fill_data_without_len_field(ext_hdr_buf, (const uint8_t*)(&ext_hdr->msessage_id), sizeof(ext_hdr->msessage_id));
    }

    if (NGL_IS_HTYP_WSID(htyp)) {
        ext_hdr_buf = ngl_ext_hdr_fill_data_without_len_field(ext_hdr_buf, (const uint8_t*)(&ext_hdr->session_id), sizeof(ext_hdr->session_id));
    }

    msg->base_header.htyp |= NGL_HTYP_GET_EXT_HDR_FLAGS(htyp);

    //todo, check and fill  proccess id
    return NGL_ErrOK;
}

ngl_error_t ngl_message_get_extension_header(ngl_message_t* msg, ngl_extension_header_t* ext_hdr) {
    if (!msg || !ext_hdr) {
        return NGL_ErrInvalidArgument;
    }

    uint16_t htyp = msg->base_header.htyp;

    if (!NGL_HTYP_HAS_EXT_HDR(htyp)) {
        return NGL_ErrNotFound;
    }

    const uint8_t* ext_hdr_buf = msg->extension_header;

    if (NGL_IS_HTYP_WHID(htyp)) {
        ext_hdr_buf = ngl_ext_hdr_read_data(ext_hdr_buf, ext_hdr->host_name, &ext_hdr->host_name_len);
    }

    if (NGL_IS_HTYP_WACID(htyp)) {
        ext_hdr_buf = ngl_ext_hdr_read_data(ext_hdr_buf, ext_hdr->application_id, &ext_hdr->appid_len);
        ext_hdr_buf = ngl_ext_hdr_read_data(ext_hdr_buf, ext_hdr->context_id, &ext_hdr->ctxid_len);
    }

    if (NGL_IS_HTYP_WMID(htyp)) {
        ext_hdr_buf = ngl_ext_hdr_read_data_without_len_field(ext_hdr_buf, (uint8_t*)(&ext_hdr->msessage_id), sizoef(ext_hdr->msessage_id));
    }

    if (NGL_IS_HTYP_WSID(htyp)) {
        ext_hdr_buf = ngl_ext_hdr_read_data_without_len_field(ext_hdr_buf, (uint8_t*)(&ext_hdr->session_id), sizoef(ext_hdr->session_id));
    }

    //todo, check and read  proccess id
    ext_hdr->htyp = NGL_HTYP_GET_EXT_HDR_FLAGS(htyp);

    return NGL_ErrOK;
}


uint8_t* ngl_message_get_extension_header_buffer(ngl_message_t* msg) {
    if (!msg) {
        return NULL;
    }

    return msg->extension_header;
}

uint16_t ngl_message_get_extension_header_buffer_size(ngl_message_t* msg) {
    if (!msg) {
        return 0;
    }

    return msg->ext_hdr_len;
}


ngl_error_t ngl_message_set_payload(ngl_message_t* msg, const uint8_t* payload, uint16_t size) {
    if (!msg || !payload || (size == 0)) {
        return NGL_ErrInvalidArgument;
    }

    uint16_t buf_len = msg->base_header.mlen - NGL_BASE_HDR_LEN - msg->ext_hdr_len;

    if (size != buf_len) { //outof range
         return NGL_ErrOutOfRange;
    }

    memcpy(msg->payload, payload, size);
    
    return NGL_ErrOK;
}

uint8_t* ngl_message_payload(ngl_message_t* msg, uint16_t* size) {
    if (!msg || !size) {
        return NULL;
    }

    *size = msg->payload_len;
    return msg->payload;
}

uint8_t* ngl_message_payload_buffer(ngl_message_t* msg) {
    if (!msg) {
        return NULL;
    }

    return msg->payload;
}

uint16_t ngl_message_payload_buffer_size(ngl_message_t* msg) {
    if (!msg) {
        return 0;
    }

    return msg->payload_len;
}


ngl_error_t ngl_message_realloc_payload_buffer(ngl_message_t* msg, uint16_t new_size) {
    if (!msg || (new_size == 0)) { //todo refine me, is new_size really cann't be 0?
        return NGL_ErrInvalidArgument;
    }

    uint32_t new_buf_size = NGL_BASE_HDR_LEN + msg->ext_hdr_len + new_size + NGL_MSG_EXTENSION_DATA_SIZE;

    if (new_buf_size > (0xFFFF + NGL_MSG_EXTENSION_DATA_SIZE)) { /** out of uint16_t range */
        return NGL_ErrOutOfRange;
    }

    if (new_buf_size <= msg->length) {
        msg->payload_len = new_size;
        msg->base_header.mlen = new_buf_size - NGL_MSG_EXTENSION_DATA_SIZE;
    } else {
        uint8_t* new_buf = (uint8_t*)realloc(msg->buffer, new_buf_size);

        if (!new_buf) {
            return NGL_ErrInsufficientResources;
        }

        msg->base_header.mlen = new_buf_size - NGL_MSG_EXTENSION_DATA_SIZE;
        ngl_msg_initialize_with_buffer(msg, new_buf, new_buf_size);
    }

    return NGL_ErrOK;
}

uint8_t* ngl_message_serialized_buffer(ngl_message_t* msg, uint16_t* size) {
    if (!msg || !size) {
        return NULL;
    }

    *size = msg->base_header.mlen;
    memcpy(msg->buffer + NGL_MSG_PREFIX_SIZE, &msg->base_header, NGL_BASE_HDR_LEN);
    return msg->buffer;
}

uint16_t ngl_extension_header_get_length(const ngl_extension_header_t* ext_hdr) {
    uint16_t len = 0;
    uint16_t htyp = ext_hdr->htyp;
    if (NGL_IS_HTYP_WHID(htyp)) {
        len += ext_hdr->host_name_len;
    }

    if (NGL_IS_HTYP_WACID(htyp)) {
        len += ext_hdr->appid_len + ext_hdr->ctxid_len;
    }

    if (NGL_IS_HTYP_WMID(htyp)) {
        len += sizeof(ext_hdr->msessage_id);
    }

    if (NGL_IS_HTYP_WSID(htyp)) {
        len += sizeof(ext_hdr->session_id);
    }

    //todo process 'process id'
    return len;
}
